package gui;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.JLabel;

/**
 * @author Ramy Refky 
 * */
public class ComponentGraphics extends JLabel{

	private static final long serialVersionUID = 1L;
	
	int originalWidth;
	int originalHeight;
	int originalXLoc;
	int originalYLoc;
	int numOfInputs;
	int numOfOutputs;
	int newWidth;
	int newHeight;
	int newXLoc;
	int newYLoc;
	
	MainGui mg;
	ComponentLabel compLabel;
	ArrayList<ComponentPin> inputPins;
	ArrayList<ComponentPin> outputPins;
	
	/**
	 * Constructor initializes component with given bounds and sets input and output pins.
	 * (This constructor sets number of output pins to 1).
	 * @param MainGui, XLocation, YLocation, Width, Height, NumberOfInputs
	 * */
	public ComponentGraphics(MainGui mg,int originalXLoc,int originalYLoc,int originalWidth,int originalHeight,int numOfInputs){
		
		this.mg=mg;
		
		this.originalWidth=originalWidth;
		this.originalHeight=originalHeight;
		this.originalXLoc=originalXLoc;
		this.originalYLoc=originalYLoc;
		this.numOfInputs=numOfInputs;
		this.numOfOutputs=1;
		
		newWidth=originalWidth;
		newHeight=originalHeight;
		newXLoc=originalXLoc;
		newYLoc=originalYLoc;
		
		setBounds(newXLoc,newYLoc,originalWidth,originalHeight);
		setVisible(true);
		
		inputPins = new ArrayList<ComponentPin>();
		outputPins = new ArrayList<ComponentPin>();
		
		compLabel=new ComponentLabel(newXLoc,newYLoc,originalWidth,originalHeight);
		this.add(compLabel);

		setInputPins(numOfInputs);
		setOutputPins(numOfOutputs);
		
		repaint();
	}
	/**
	 * Constructor initializes component with given bounds and sets input and output pins.
	 * @param MainGui, XLocation, YLocation, Width, Height, NumberOfInputs, NumberOfOutputs 
	 * */
	public ComponentGraphics(MainGui mg,int originalXLoc,int originalYLoc,
			int originalWidth,int originalHeight,int numOfInputs,int numOfOutputs){
		
		this.mg=mg;
		
		this.originalWidth=originalWidth;
		this.originalHeight=originalHeight;
		this.originalXLoc=originalXLoc;
		this.originalYLoc=originalYLoc;
		this.numOfInputs=numOfInputs;
		this.numOfOutputs=numOfOutputs;
		
		newWidth=originalWidth;
		newHeight=originalHeight;
		newXLoc=originalXLoc;
		newYLoc=originalYLoc;
		
		setBounds(newXLoc,newYLoc,originalWidth,originalHeight);
		setVisible(true);
		
		inputPins = new ArrayList<ComponentPin>();
		outputPins = new ArrayList<ComponentPin>();
		
		compLabel=new ComponentLabel(newXLoc,newYLoc,originalWidth,originalHeight);
		this.add(compLabel);
	
		setInputPins(numOfInputs);
		setOutputPins(numOfOutputs);
		
		repaint();
	}
	/**
	 * Constructor initializes component with given bounds.
	 * (This constructor sets number of input and output pins to zero).
	 * @param MainGui, XLocation, YLocation, Width, Height
	 * */
	public ComponentGraphics(MainGui mg,int originalXLoc,int originalYLoc,int originalWidth,int originalHeight){
		
		this.mg=mg;
		
		this.originalWidth=originalWidth;
		this.originalHeight=originalHeight;
		this.originalXLoc=originalXLoc;
		this.originalYLoc=originalYLoc;
		this.numOfInputs=0;
		this.numOfOutputs=0;
		
		newWidth=originalWidth;
		newHeight=originalHeight;
		newXLoc=originalXLoc;
		newYLoc=originalYLoc;
		
		setBounds(newXLoc,newYLoc,originalWidth,originalHeight);
		setVisible(true);
		
		inputPins = new ArrayList<ComponentPin>();
		outputPins = new ArrayList<ComponentPin>();
		
		compLabel=new ComponentLabel(newXLoc,newYLoc,originalWidth,originalHeight);
		this.add(compLabel);

		repaint();
	}
	/**
	 * Gets the number of input pins connected to this component.
	 * @return int
	 * */
	public int getNumberOfInputs(){
		return numOfInputs;
	}
	/**
	 * Gets the number of output pins connected to this component.
	 * @return int
	 * */
	public int getNumberOfOutputs(){
		return numOfOutputs;
	}
	/**
	 * Gets the input ComponentPins connected to this component.
	 * @return ArrayList
	 * */
	public ArrayList<ComponentPin> getInputPins(){
		return inputPins;
	}
	/**
	 * Gets the output ComponentPins connected to this component.
	 * @return ArrayList
	 * */
	public ArrayList<ComponentPin> getOutputPins(){
		return outputPins;
	}
	/**
	 * Manually adds an input ComponentPin with given bounds.
	 * */
	public void addInputPin(int startX,int startY,int endX,int endY){
		ComponentPin pin = new ComponentPin(mg,this,startX,startY,endX,endY);
		inputPins.add(pin);
		this.add(inputPins.get(inputPins.size()-1));
	}
	/**
	 * Manually adds an output ComponentPin with given bounds.
	 * */
	public void addOutputPin(int startX,int startY,int endX,int endY){
		ComponentPin pin = new ComponentPin(mg,this,startX,startY,endX,endY);
		pin.setTypeAsOutput();
		outputPins.add(pin);
		this.add(outputPins.get(outputPins.size()-1));
	}
	/**
	 * Gets the first element in the ArrayList of output ComponentPins.
	 * @return ComponentPin 
	 * */
	public ComponentPin getOutputPin(){
		return outputPins.get(0);
	}
	/**
	 * Gets the top-left endPoint of this component.
	 * @return Point
	 * */
	public Point getLocation(){
		return new Point(newXLoc,newYLoc); 
	} 
	/**
	 * Adjusts the bounds of this component after zooming in or out.
	 * */
	public void zoomAdjust(Double zoomFactor){
		newWidth=(int) (originalWidth*zoomFactor);
		newHeight=(int) (originalHeight*zoomFactor);
		newXLoc=(int) (originalXLoc*zoomFactor);
		newYLoc=(int) (originalYLoc*zoomFactor);
		setBounds(newXLoc,newYLoc,newWidth,newHeight);
		compLabel.zoomAdjust(zoomFactor);
		adjustPins();
		repaint();
	}
	/**
	 * Initializes the input ComponentPins and positions them accordingly.
	 * */
	public void setInputPins(int numOfInputs){
		
		if(numOfInputs<=8){
			if(numOfInputs%2==1){
				for(int i=1;i<=numOfInputs;i++){
					int h = i*originalHeight/(numOfInputs+1)-1;
					ComponentPin pin = new ComponentPin(mg,this,0,h,originalWidth/2,h);
					inputPins.add(pin);
					this.add(inputPins.get(inputPins.size()-1));
				}
			}
			else{
				for(int i=1;i<=numOfInputs;i++){
					int h = (2*(i-1)+1)*originalHeight/(numOfInputs*2) -1;
					ComponentPin pin = new ComponentPin(mg,this,0,h,originalWidth/2,h);
					inputPins.add(pin);
					this.add(inputPins.get(inputPins.size()-1));
				}
			}
		}
		else{
			for(int i=1;i<=numOfInputs;i++){
				int h = originalHeight/(2) -1;
				ComponentPin pin = new ComponentPin(mg,this,0,h,originalWidth/2,h);
				pin.setAsGrouped();
				inputPins.add(pin);
				this.add(inputPins.get(inputPins.size()-1));
			}
		}
		repaint();
	}
	/**
	 * Initializes the output ComponentPins and positions them accordingly.
	 * */
	public void setOutputPins(int numOfOutputs){
		
		if(numOfOutputs<=8){
			if(numOfOutputs%2==1){
				for(int i=1;i<=numOfOutputs;i++){
					int h = i*originalHeight/(numOfOutputs+1)-1;
					ComponentPin pin = new ComponentPin(mg,this,originalWidth/2,h,originalWidth,h);
					pin.setTypeAsOutput();
					outputPins.add(pin);
					this.add(outputPins.get(outputPins.size()-1));
				}
			}
			else{
				for(int i=1;i<=numOfOutputs;i++){
					int h = (2*(i-1)+1)*originalHeight/(numOfOutputs*2) -1;
					ComponentPin pin = new ComponentPin(mg,this,originalWidth/2,h,originalWidth,h);
					pin.setTypeAsOutput();
					outputPins.add(pin);
					this.add(outputPins.get(outputPins.size()-1));
				}
			}
		}
		else{
			for(int i=1;i<=numOfOutputs;i++){
				int h = originalHeight/(2) -1;
				ComponentPin pin = new ComponentPin(mg,this,originalWidth/2,h,originalWidth,h);
				pin.setTypeAsOutput();
				pin.setAsGrouped();
				outputPins.add(pin);
				this.add(outputPins.get(outputPins.size()-1));
			}
		}
		repaint();
	}
	/**
	 * Positions the input and output ComponentPins accordingly.
	 * */
	public void adjustPins(){
		
		int inputCount=getNumberOfInputs();
		int outputCount=getNumberOfOutputs();
		
		if(inputCount<=8){
			if(inputCount%2==1){
				for(int i=1;i<=inputCount;i++){
					int h = i*newHeight/(inputCount+1)-1;
					inputPins.get(i-1).zoomAdjust(0,h,newWidth/2,h);
				}
			}
			else{
				for(int i=1;i<=inputCount;i++){
					int h = (2*(i-1)+1)*newHeight/(inputCount*2) -1;
					inputPins.get(i-1).zoomAdjust(0,h,newWidth/2,h);
				}
			}
		}
		else{
			for(int i=1;i<=inputCount;i++){
				int h = newHeight/(2) -1;
				inputPins.get(i-1).zoomAdjust(0,h,newWidth/2,h);
			}
		}
		
		if(outputCount<=8){
			if(outputCount%2==1){
				for(int i=1;i<=outputCount;i++){
					int h = i*newHeight/(outputCount+1)-1;
					outputPins.get(i-1).zoomAdjust(newWidth/2,h,newWidth,h);
				}
			}
			else{
				for(int i=1;i<=outputCount;i++){
					int h = (2*(i-1)+1)*newHeight/(outputCount*2) -1;
					outputPins.get(i-1).zoomAdjust(newWidth/2,h,newWidth,h);
				}
			}
		}
		else{
			for(int i=1;i<=outputCount;i++){
				int h = newHeight/(2) -1;
				outputPins.get(i-1).zoomAdjust(newWidth/2,h,newWidth,h);
			}
		}
		repaint();
	}
	/**
	 * Checks if this component intersects the area of a given rectangle.
	 * @return boolean
	 * */
	public boolean checkIfSelected(Rectangle box){
		
		boolean isSelected=false;
		Point topLeft = new Point();
		topLeft.x=newXLoc;
		topLeft.y=newYLoc;
		
		Point bottomRight = new Point();
		bottomRight.x=this.getX()+newWidth;
		bottomRight.y=this.getY()+newHeight;
		
		Rectangle componentRectangle = new Rectangle();
		componentRectangle.setFrameFromDiagonal(topLeft, bottomRight);
		
		Rectangle2D intersectionRectangle = box.createIntersection(componentRectangle); 
		if(intersectionRectangle.isEmpty()){
			deselect();
		}
		else{
			select();
			isSelected=true;
		}
		repaint();
		return isSelected;
	}
	/**
	 * Calls the ComponentLabel's select() method to highlight the component indicating selection.
	 * */
	public void select(){
		compLabel.select();
	}
	/**
	 * Calls the ComponentLabel's deselect() method to draw the component without highlighting it.
	 * */
	public void deselect(){
		compLabel.deselect();
	}
	/**
	 * Calls the ComponentLabel's isSelected() method to check if the component is drawn highlighted.
	 * @return boolean
	 * */
	public boolean isSelected(){
		return compLabel.isSelected();
	}
	/**
	 * Translates this component along the x and y axis.
	 * */
	public void moveToLocation(int xTranslation, int yTranslation){
		newXLoc+=xTranslation;
		newYLoc+=yTranslation;
		originalXLoc+=xTranslation;
		originalYLoc+=yTranslation;
		setBounds(newXLoc,newYLoc,newWidth,newHeight);
		repaint();
	}
}

/**
 * @author Ramy Refky 
 * */
class ComponentLabel extends JLabel{

	private static final long serialVersionUID = 1L;
	
	int originalWidth;
	int originalHeight;
	int xLoc;
	int yLoc;
	int newWidth;
	int newHeight;
	boolean selected;
	
	/**
	 * Constructor initializes the component's label given its bounds.
	 * @param XLocation, YLocation, Width, Height
	 * */
	public ComponentLabel(int xLoc,int yLoc,int originalWidth, int originalHeight){
		this.originalWidth=originalWidth;
		this.originalHeight=originalHeight;
		this.xLoc=xLoc;
		this.yLoc=yLoc;
		newWidth=originalWidth;
		newHeight=originalHeight;
		selected=false;
		setBounds(0,0,originalWidth,originalHeight);
		setVisible(true);
		repaint();
	}
	/**
	 * Adjusts the bounds of the component's label according to the zoom factor.
	 * */
	public void zoomAdjust(Double zoomFactor){
		newWidth=(int) (originalWidth*zoomFactor);
		newHeight=(int) (originalHeight*zoomFactor);
		setBounds(0,0,newWidth,newHeight);
		repaint();
	}
	/**
	 * Sets the component's label to be drawn highlighted when painted.
	 * */
	public void select(){
		selected=true;
		repaint();
	}
	/**
	 * Sets the component's label to be drawn without highlighting it when painted.
	 * */
	public void deselect(){
		selected=false;
		repaint();
	}
	/**
	 * Checks if the component's label is drawn highlighted or not.
	 * @return boolean
	 * */
	public boolean isSelected(){
		return selected;
	}
}
